#!/usr/bin/env python


########################################################################
# COPYRIGHT_BEGIN
#
#           mical
#           Minimal iCalendar utilities
#
# Copyright (C) 2007, David Arnold
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# COPYRIGHT_END
########################################################################
# $ZeroXOne: mical/micshow,v 1.9 2007/06/04 17:13:05 d Exp $
########################################################################

try:
  from xml.etree import ElementTree # for Python 2.5 users
except ImportError:
  from elementtree import ElementTree


import sys
import re
import os
import logging
import email
import tempfile
import time
import vobject
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom
import string

from dateutil.tz import tzlocal
from optparse import OptionParser


opts = None


def parseNewEvent(ne):

    re_new = re.compile(r'^(\d{4}-\d{2}-\d{2})\s(\d{1,2}:\d{1,2}-\d{1,2}:\d{1,2})\s(\d+)\s(\[(.*?)\]\s)?(.*?)$')
    try:
        re_new_res = re_new.search(ne)
        ddate, ttime, minutes, none, where, summary = re_new_res.groups()
        logging.debug('ddate: %s' % ddate)
        tt_start = ttime[:5]
        tt_end   = ttime[6:]
        logging.debug('start time: %s' % tt_start)
        logging.debug('end time:   %s' % tt_end)
        logging.debug('minutes: %s' % minutes)
        logging.debug('where: %s' % where)
        logging.debug('summary: %s' % summary)

        if not where:
            where = ''

        tt = time.strptime("%s %s" % (ddate,tt_start), '%Y-%m-%d %H:%M')
        s_secs = time.mktime(tt)
        tt = time.strptime("%s %s" % (ddate,tt_end), '%Y-%m-%d %H:%M')
        e_secs = time.mktime(tt)

    except:
        logging.error('error in parsing new event')
        os._exit(1)

    try:
        insertInGoogleCal('', unicode(summary, "utf-8"),
                              unicode(where, "utf-8"),
                          '', s_secs, e_secs, int(minutes))
    except:
        logging.error('error in adding new event')
        os._exit(1)



def InsertSingleEvent(calendar_service, title='One-time Tennis with Beth',
                      content='Meet for a quick lesson', where='On the courts',
                      start_time=None, end_time=None):

    event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=title)
    event.content = atom.Content(text=content)
    event.where.append(gdata.calendar.Where(value_string=where))

    if start_time is None:
      # Use current time for the start_time and have the event last 1 hour
      start_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
      end_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(time.time() + 3600))
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, '/calendar/feeds/default/private/full')

    #print 'New single event inserted: %s' % (new_event.id.text,)
    #print '\tEvent edit URL: %s' % (new_event.GetEditLink().href,)
    #print '\tEvent HTML URL: %s' % (new_event.GetHtmlLink().href,)

    return new_event



def AddReminder(calendar_service, event, minutes=10):

    for a_when in event.when:
        if len(a_when.reminder) > 0:
            a_when.reminder[0].minutes = minutes
        else:
            a_when.reminder.append(gdata.calendar.Reminder(minutes=minutes))

    logging.info('adding %d minute reminder to event' % (minutes,))

    return calendar_service.UpdateEvent(event.GetEditLink().href, event)



def insertInGoogleCal(method, summary, location, desc, start_sec, end_sec, minutes=15):

    global opts

    try:
        calendar_service = gdata.calendar.service.CalendarService()
        calendar_service.email = opts.username
        calendar_service.password = opts.password
        calendar_service.source = 'My Google Calendar Importer'
        calendar_service.ProgrammaticLogin()
        logging.info('loggin to google')
    except:
        logging.info('unable to loggin to google')
        sys.exit(1)

    start_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z',  time.gmtime(start_sec))
    end_time = time.strftime('%Y-%m-%dT%H:%M:%S.000Z',  time.gmtime(end_sec))

    try:
        new_event = InsertSingleEvent(calendar_service, summary, desc, location, start_time, end_time)
        logging.info('event created')
    except:
        logging.info('unable to create event')
        sys.exit(1)


    try:
        AddReminder(calendar_service,new_event,minutes)
        return new_event
    except:
        logging.info('unable to add reminder')
        sys.exit(1)



def processCal(event_string):

    try:
        cal = vobject.readOne(event_string)
        logging.debug('event parsed successfuly')
    except:
        logging.error('wrong calendar format (Did you use -m option?)')
        sys.exit(1)

    event = cal.vevent 

    method    = ''
    summary   = ''
    location  = ''
    desc      = ''
    start_sec = 0
    end_sec   = 0

    method = cal.method.value.capitalize()
    logging.info("Method: %s" % method)

    # Summary
    if hasattr(event, 'summary'):
        summary = event.summary.value
        logging.info("Sumary: %s" % summary)

    # Location
    if hasattr(event, 'location'):
        location = event.location.value
        logging.info("Location: %s" % location)

    # Time
    if hasattr(event, 'dtstart'):
        start = event.dtstart.value
        local_start = start.astimezone(tzlocal())

        if hasattr(event, 'dtend'):
            end = event.dtend.value
            local_end = end.astimezone(tzlocal())
        elif hasattr(event, 'duration'):
            end = start + event.duration.value
            local_end = end.astimezone(tzlocal())

        s  = local_start.strftime("%l:%M %p").strip()
        s += " - " + local_end.strftime("%l:%M %p %Z").strip()
        logging.info("Time: %s" % s)
        
        start_sec = time.mktime(local_start.timetuple())
        end_sec   = time.mktime(local_end.timetuple())
        logging.info("Time: %d - %d" % (start_sec, end_sec))
        

    # Description
    if hasattr(event, 'description'):
        desc = event.description.value
        # Remove Outlook-specific description prefix if present
        outlook_nonsense = desc.find("*~*~*~*~*~*~*~*~*~*")
        if outlook_nonsense > 0:
            desc = desc[outlook_nonsense+19:].strip()

        if len(desc) > 0:
            desc = desc.encode("utf-8", 'replace')
            desc = desc.replace('\r', '')

            desc = reduce(lambda line, word: '%s%s%s' % (line, ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n', 1)[0]) >= 60)], word), desc.split(' '))
            
            logging.info("Description: \n%s" % desc)


    if summary and start_sec and end_sec:
        insertInGoogleCal(method, summary, location, desc, start_sec, end_sec, 15)
    else:
        logging.error("not enouht information")
        sys.exit(1)



def processEmail(email_input):

    msg = email.message_from_string(email_input)

    msg_from = msg.get("From")
    msg_subj = msg.get("Subject")

    logging.info("From: %s" % msg_from)
    logging.info("Subject: %s" % msg_subj)

    counter = 1
    for part in msg.walk():

        if part.get_content_maintype() == 'multipart':
            continue

        if part.get_content_type() == 'text/calendar':
            fp = tempfile.TemporaryFile()
            fp.write(part.get_payload(decode=1))
            
            fp.seek(0)
            processCal(fp.read())
            fp.close()



def processInput(my_input):

    global opts

    if opts.mail:
        processEmail(my_input)
    else:
        processCal(my_input)



def main():

    global opts


    #
    # default variables
    # 
    prg        = os.path.basename( sys.argv[0] )
    usage      = prg  + ' [ <OPTIONS> ] <ARGS>'

 
    #
    # parse command line, result is stored to 'opts'
    #
    parser = OptionParser( usage, version="%s version 0.1" % prg )

    parser.add_option( '-v', '--verbose', action='store_true', dest="verbose",
                       default=False, help='be verbose' )
    parser.add_option( '-u', '--username', action='store', dest="username",
                       default='', help='set Google account USERNAME (eg.: smith@gmail.com)' )
    parser.add_option( '-p', '--password', action='store', dest="password",
                       default='', help='set Google account PASSWORD' )
    parser.add_option( '-m', '--mail', action='store_true', dest="mail",
                       default=False, help='process mail input(s)' )
    parser.add_option( '-P', '--proxy', action='store', dest="proxy",
                       default='', help='set PROXY as https proxy' )
    parser.add_option( '-n', '--new', action='store', dest="new",
                       default='', help='add NEW event' )
 

    opts, args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(levelname)s %(message)s',
                        filemode='w')

    if opts.proxy:
        os.environ['http_proxy']  = opts.proxy
        os.environ['https_proxy'] = opts.proxy


    if opts.new:
        logging.info('new event: %s' % opts.new)
        parseNewEvent(opts.new)
        sys.exit(0)



    if not sys.stdin.isatty():
        logging.info('stdin detected')
        try:
            my_input = sys.stdin.read()
            logging.info('stdin read')
        except:
            logging.error('something wrong with stdin')
            sys.exit(1)
        processInput(my_input)
    else:
        for filename in args:
            logging.info('processing: %s' % filename)
            try:
                my_input = file(filename, 'r').read()
                logging.info('file %s read' % filename)
            except:
                logging.error('something wrong with %s' % filename)
                sys.exit(1)
            processInput(my_input)



if __name__ == '__main__':
    main()

